import { DbProvider } from './db-provider';
import { Sqlifier } from './sqlifier';
import { SQL } from '../sql-builder';
import {
  RowObject,
  WhereObject,
  Condition,
  XRowset,
  Sort,
  SortObject,
  Parameter,
} from '../sql';
import { Executor } from './executor';
import { Command, QueryResult } from './types';
/**
 * 事务隔离级别
 */
export enum ISOLATION_LEVEL {
  READ_COMMIT = 'READ_COMMIT',
  READ_UNCOMMIT = 'READ_UNCOMMIT',
  REPEATABLE_READ = 'REPEATABLE_READ',
  SERIALIZABLE = 'SERIALIZABLE',
  SNAPSHOT = 'SNAPSHOT',
}

export class AbortError extends Error {
  code: string = 'ABORT';
}

export interface SelectOptions<T extends RowObject = any> {
  where?:
    | WhereObject<T>
    | Condition
    | ((table: Readonly<XRowset<T>>) => Condition);
  top?: number;
  offset?: number;
  limit?: number;
  distinct?: boolean;
  sorts?: Sort[] | SortObject<T> | ((rowset: XRowset<T>) => Sort[]);
}

// export interface QueryHandler {
//   (sql: string, params?: Parameter<Scalar, string>[]): Promise<
//     QueryResult<any, any, any>
//   >;
// }

export const DefaultConnectionOptions: Partial<ConnectOptions> = {
  host: 'localhost',
  requestTimeout: 15000,
  connectionTimeout: 30000,
};

abstract class ConnectionClass extends Executor {
  constructor(public provider: DbProvider, options: ConnectOptions) {
    super();
    this.options = Object.assign({}, DefaultConnectionOptions, options);
  }

  public readonly options: ConnectOptions;

  get sqlifier(): Sqlifier {
    return this.provider.sqlifier;
  }

  protected internalQuery(...args: any) {
    if (!this.opened) {
      throw new Error(`Connection is not opened yet.`);
    }
    return super.internalQuery(...args);
  }

  /**
   * 是否活动的
   */
  activated: boolean = true;

  active() {
    this.activated = true;
  }

  declare on: ((event: 'open', listener: () => void) => this) &
    ((event: 'opened', listener: () => void) => this) &
    ((event: 'close', listener: () => void) => this) &
    ((event: 'closed', listener: () => void) => this) &
    ((event: 'command', listener: (cmd: Command) => void) => this) &
    ((event: 'error', listener: (error: Error) => any) => this) &
    ((
      event: 'begin',
      listener: (connection: this, id: number) => void
    ) => this) &
    ((
      event: 'commit',
      listener: (connection: this, id: number) => void
    ) => this) &
    ((
      event: 'rollback',
      listener: (connection: this, id: number) => void
    ) => this);

  declare off: ((event: 'open', listener: () => void) => this) &
    ((event: 'opened', listener: () => void) => this) &
    ((event: 'close', listener: () => void) => this) &
    ((event: 'closed', listener: () => void) => this) &
    ((event: 'command', listener: (cmd: Command) => void) => this) &
    ((event: 'error', listener: (error: Error) => any) => this) &
    ((
      event: 'begin',
      listener: (connection: this, id: number) => void
    ) => this) &
    ((
      event: 'commit',
      listener: (connection: this, id: number) => void
    ) => this) &
    ((
      event: 'rollback',
      listener: (connection: this, id: number) => void
    ) => this);

  /**
   * 开启一个事务并在代码执行完成后自动提交，遇到错误时会自动回滚
   * 用户亦可主动调用cancel来取消当前事务，并且产生一个异常中断后续代码执行
   */
  async trans<T>(
    handler: (abort: (message?: string) => void) => Promise<T>,
    isolationLevel: ISOLATION_LEVEL = ISOLATION_LEVEL.READ_COMMIT
  ): Promise<T> {
    try {
      await this.beginTrans(isolationLevel);
      const res = await handler((message: string) => {
        throw new AbortError(message || 'Abort.');
      });
      await this.commit();
      return res;
    } catch (ex) {
      if (this.inTransaction) {
        await this.rollback();
      }
      throw ex;
    }
  }

  protected abstract doBeginTrans(
    isolationLevel: ISOLATION_LEVEL
  ): Promise<void>;

  _transCount: number = 0;

  get inTransaction() {
    return this.transCount > 0;
  }

  /**
   * 当前事务嵌套层数
   */
  get transCount(): number {
    return this._transCount;
  }
  /**
   * 开启一个事务，
   * 支持事务嵌套
   */
  async beginTrans(
    isolationLevel: ISOLATION_LEVEL = ISOLATION_LEVEL.READ_COMMIT
  ): Promise<void> {
    // return this.progress(async () => {
    const id = this._transCount;
    if (this._transCount > 0) {
      await this.createSavePoint(id);
    } else {
      await this.doBeginTrans(isolationLevel);
    }
    this._transCount++;
    this.emit('begin', id);
    // });
  }

  protected abstract createSavePoint(id: number): Promise<void>;
  protected abstract rollbackSavePoint(id: number): Promise<void>;

  protected abstract doCommit(): Promise<void>;

  /**
   * 提交当前事务,如使用嵌套事务，则提交最近事务
   */
  async commit(): Promise<void> {
    if (!this.inTransaction) {
      throw Error(`Connection is not in transaction.`);
    }
    if (this._transCount === 1) {
      await this.doCommit();
    }
    this._transCount--;
    this.emit('commit', this._transCount);
  }

  protected abstract doRollback(): Promise<void>;

  async rollback(): Promise<void> {
    if (!this.inTransaction) {
      throw Error(`Connection is not in transaction.`);
    }
    const id = this._transCount - 1;
    if (id >= 1) {
      await this.rollbackSavePoint(id);
    } else {
      await this.doRollback();
    }
    this._transCount--;
    this.emit('rollback', id);
  }

  private _opened: boolean = false;

  get opened(): boolean {
    return this._opened;
  }

  protected abstract doOpen(): Promise<void>;

  open(): Promise<void> {
    if (this.opened) {
      throw new Error(`Connection is opened.`);
    }

    this.emit('open');
    return this.mutex(async () => {
      await this.doOpen();
      this._opened = true;
      this.emit('opened');
    });
  }

  protected abstract doClose(): Promise<void>;

  /**
   * 重置连接，用于连接池
   */
  protected abstract reset(): Promise<void>;
  /**
   * 等待所有请求完成后关闭连接
   */
  close(): Promise<void> {
    // if (this.inTransaction) {
    //   throw new Error(
    //     `Connection is in transaction, can't close it before commit or rollback.`
    //   );
    // }
    // if (this._inMutex) {
    //   throw new Error(`Connection is in progress.`);
    // }
    this.emit('close');
    return this.mutex(async () => {
      await this.doClose();
      this._opened = false;
      this.emit('closed');
    });
  }

  protected abstract doShutdown(): void;

  /**
   * 强制关闭所有连接（慎用）
   */
  shutdown(): void {
    this.doShutdown();
    this._opened = false;
    this._transCount = 0;
  }

  /**
   * 获取当前默认架构
   * @returns
   */
  async getSchemaName(): Promise<string> {
    return (await this.queryScalar(SQL.select(SQL.defaultSchema())))!;
  }

  /**
   * 获取当前数据库
   */
  async getDatabaseName(): Promise<string> {
    if (!this.opened) {
      if (this.options.database) {
        return this.options.database;
      }
    }
    return (await this.queryScalar(SQL.select(SQL.currentDatabase())))!;
  }

  /**
   * 变更所在数据库
   * 当为null时表示登录用户默认数据库
   */
  async changeDatabase(name: string): Promise<void> {
    if (name === this.options.database) return;
    this.options.database = name;
    if (this.opened) {
      await this.close();
      await this.open();
    }
  }

  /**
   * 切换到系统数据库，便于创建、删除其它数据操作。
   */
  abstract changeToSysdb(): Promise<void>;

  protected async mutex<T>(action: () => Promise<T>): Promise<T> {
    if (this._inMutex) {
      throw new Error(`Connection is in progress.`);
    }
    this._inMutex = true;
    try {
      return action();
    } catch (ex) {
      this.emit('error', ex);
      throw ex;
    } finally {
      this._inMutex = false;
    }
  }

  private _inMutex: boolean = false;
}

export type ConnectionConstructor = typeof ConnectionClass;

export const Connection: ConnectionConstructor = ConnectionClass;

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Connection extends ConnectionClass {}

export type ConnectOptions = {
  /**
   * 数据库方言(必须是已注册的)，与driver二选一，必须安装相应的驱动才可正常使用
   */
  dialect?: string;
  /**
   * 驱动程序，与dialect二选一，优先使用driver
   */
  provider?: DbProvider;

  /**
   * 主机名
   */
  host: string;
  /**
   * 端口号
   */
  port?: number;
  /**
   * 连接用户名
   */
  user: string;
  /**
   * 密码
   */
  password: string;
  /**
   * 数据库名称
   */
  database?: string;

  /**
   * 连接超时时长，单位: ms，默认为15000ms
   */
  connectionTimeout?: number;
  /**
   * 单个查询超时时长,单位: ms，默认为15000ms
   */
  requestTimeout?: number;

  /**
   * 多条语句执行，针对不同的SQL语言采取不同的策略
   * MSSQL 本身支持语句块查询，并支持变量声明等操作
   * MYSQL 则需要开启后方可支持，但不支持局部变量声明等操作;
   * PGSQL 则需要声明为DO语句，支持局部变量声明等操作，但不支持行集返回及输出参数，仅可通过临时表等方式返回；
   * 而lubejs则在客户端层面支持多语句执行(SQL字符串除外)，实际上则是执行多条语句并一次返回。
   */
  // multiStatement: boolean;
};

export const INSERT_MAXIMUM_ROWS = 1000;
